class Vue {
  // 构造方法
  constructor(opts) {
    if (!opts.el) throw new Error('参数缺少 el配置')
    this.$el = document.querySelector(opts.el)
    this.$data = opts.data
    // 数据代理
    observe(opts.data)
    // 模板编译
    compileTemplate(this.$el, this)
  }
}

// 编译模板 把dom转为虚拟dom
function compileTemplate(el, vm) {
  // 文档碎片,存储在内存中，不会直接在界面上显示
  let fragment = document.createDocumentFragment()
  // 把dom全部存储到fragment中
  let child;
  while (child = el.firstChild) {
    compileRender(child, vm)
    // 一定要写此句,添加到fragment后，会把dom清除掉
    fragment.appendChild(child)
  }
  el.appendChild(fragment)
}

// html编译解析
function compileRender(node, vm) {
  // 判断它是元素还是文档节点
  if (node.nodeType === 1) {// 元素
    Array.from(node.attributes).forEach(({ name, value }) => {
      if (/^v-/.test(name)) {
        if (node.nodeName === 'INPUT') {
          node.value = vm.$data[value];
          // 放一个观察的人
          new Watcher(node, value, vm)

          // 绑定一个事件
          node.addEventListener('input', function () {
            vm.$data[value] = this.value.trim()
          }, false)
        } else {
          node.innerHTML = vm.$data[value]
        }
      }
    })
    // 问一下它有没有子元素
    node.childNodes.forEach(child => compileRender(child, vm))
  } else if (node.nodeType === 3) {// 文档节点
    let preg = /\{\{\s*(\w+)\s*\}\}/
    let cnt = node.textContent;
    if (preg.test(cnt)) {
      cnt = cnt.replace(preg, function (arg0, arg1) {
        // 观察的人
        new Watcher(node, arg1, vm)

        return vm.$data[arg1] || ''
      });
      node.textContent = cnt
    }
  }
}



/**
 * 数据观察  -- 一定要对象,普通数据不进行观察
 */
function observe(target) {
  // 类型判断 必须为对象
  if (Object.prototype.toString.call(target) != '[object Object]') return;
  for (let key in target) {
    // 数据对象的属性代理ss
    defineReactive(target, key, target[key])
  }
}

// 代理
function defineReactive(target, key, value) {
  let dep = new Dep()
  Object.defineProperty(target, key, {
    get() {
      if (Dep.target) {
        // console.log('get')
        // 添加观察
        dep.addSubs(Dep.target)
      }
      // 获取数据时触发
      return value;
    },
    set(v) {
      if (v != value) {
        // console.log('set')
        // 设置数据时会触发
        value = v
        // 通过所有模板要更新
        dep.notify()
      }
    }
  })
}


// 观察仓库
class Dep {
  // 添加观察者,可以有多个，所以用一个队列(数组)
  constructor() {
    // 订阅的观察人员列表
    this.subscribes = []
  }

  // 添加观察人
  addSubs(watcher) {
    this.subscribes.push(watcher)
  }

  // 更新
  notify() {
    this.subscribes.forEach(watcher => watcher.update())
  }
}

// 观察者
class Watcher {
  constructor(node, key, vm) {
    // 标识
    Dep.target = this;
    this.$node = node;
    this.$key = key;
    this.$vm = vm;
    // 手动触发它的getter
    this.getValue();
    Dep.target = null;
  }
  // 它的作用就是为了得到最新的数据让getter重新触发一次，让watcher被添加到队列中
  getValue() {
    this.$value = this.$vm.$data[this.$key]
  }

  update() {
    this.getValue();
    if (this.$node.nodeType == 1) {
      this.$node.value = this.$value
    } else if (this.$node.nodeType == 3) {
      this.$node.textContent = this.$value
    }
  }
}